%     FASTENER DOCTOR - программа для лечения и конвертации крепежа
%     Copyright (C) 2018  Михаил Козлов
%     Сайт проекта: <http://www.fastenerdoctor.ru/>
%     Адрес электронной почты для связи с разработчиком:
%     <doctor.fastener@gmail.com>
% 
%     Эта программа является свободным программным обеспечением: Вы можете
%     распространять ее и/или изменять, соблюдая условия Универсальной общественной
%     лицензии GNU, опубликованной Фондом свободного программного обеспечения,
%     либо редакции 3, либо (на Ваше усмотрение) любой редакции, выпущенной позже.
% 
%     Эта программа распространяется в надежде на то, что она окажется полезной,
%     но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, включая даже подразумеваемую гарантию ТОВАРНОЙ ПРИГОДНОСТИ,
%     либо ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Ознакомьтесь с Универсальной общественной
%     лицензией GNU для получения более подробной информации.
% 
%     Вы должны были получить копию Универсальной общественной лицензии GNU
%     вместе с этой программой. Если Вы ее не получили, то перейдите по адресу:
%     <http://www.gnu.org/licenses/>.
% 
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
% 
%     This program is distributed in the hope that it will be useful,
%     but WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%     GNU General Public License for more details.
% 
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <https://www.gnu.org/licenses/>.

function DB_FASTENERS = Recognize_Fasteners()

% DB_FASTENERS{:,1}
%                ->.topology   ('bar_fastener' | 'rutman_fastener')
%  bar  rut
% | +  | + |     ->.N_layers
% | +  | + |     ->.Shank_nodes(:,1)
% | +  | + |     ->.Shank_elements(:,1)
% | [] | + |     ->.Additional_nodes(:,1)
% | [] | + |     ->.RBAR_MPCs(:,1)
% | [] | + |     ->.Head_MPC_Index         % Индекс элемента массива RBAR_MPCs
% | [] | + |     ->.Bush_elements{:,1}
% | +  | + |     ->.Offset_MPCs{:,1}
% | +  | + |     ->.Joint_nodes(:,1)
% | +  | + |     ->.Joint_topology{:,1}   ('plate' | 'plate_rbe2_sequential' | 'plate_rbe2_parallel' | 'rbe2_2d_hole' | 'rbe2_3d_hole' | 'bar_spider_2d_hole' | 'bar_spider_3d_hole')
% | +  | + |     ->.Joint_mesh{:,1}
%                                ->.MPC_ID
%                                ->.Bar_Spider_Elm_list(:,1)
%                                ->.Bar_Spider_Conn_Nodes_list(:,1)
%                                ->.Element_list(:,1)

global ErrCode;

global DB_NODES;
global DB_ELEMENTS;
global DB_MPC;
global DB_MATERIALS;
global DB_PSETS;

global AT_NODES;
global AT_ELEMENTS;
global AT_MPC;
global AT_MATERIALS;
global AT_PSETS;

global AST_PSETS;

global K_Relative_Tolerance;
global I12_Tolerance;
global I1_I2_Relative_Tolerance;
global J_I1_I2_Relative_Tolerance;
global RA_RI_Relative_Tolerance;
global Bolt_shank_deg_tolerance;
global Plate_normal_deg_tolerance;
global Plate_mat_ori_deg_tolerance;
global Bolt_plate_deg_tolerance;
global Equidistant_hole_tolerance;
global Offset_relative_tolerance;

global Diagnostic_Data;

global Global_equivalence_tolerance;

DB_FASTENERS = [];

% Создание структуры диагностической информации
Diagnostic_Data.Circular_Cross_Section_Elements = [];
Diagnostic_Data.Recognized_Chains = [];
Diagnostic_Data.Chain_Screening_Marker_Table = [];
Diagnostic_Data.Chain_Screening_Reason_Data = [];

% --- Поиск балочных элементов круглого сечения ---

fprintf (1, 'Поиск балок круглого сечения...         ');

% Первоначальное выделение памяти
Bolt_Shank_elements = zeros(size(DB_ELEMENTS,1),1);
Bolt_Shank_counter = 0;

% Цикл по всем элементам
for i=1:size(DB_ELEMENTS,1)
    
    % Если элемент удален из базы, то переходим к следующему
    if (DB_ELEMENTS{i}.ID == -1)
        continue;
    end
    
    % Если у элемента пустой property set, то переходим к следующему
    if (isempty(DB_ELEMENTS{i}.Prop_ID))
        continue;
    end
    
    % Число узлов равно двум
    if (DB_ELEMENTS{i}.N_nodes ~= 2)
        continue;
    end
    
    % Топология элемента Bar
    if (~strcmp(DB_ELEMENTS{i}.type, 'Bar'))
        continue;
    end
    
    % Фильтр по типу property set
    switch DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.type
        case {'PBAR', 'PBARL', 'PBEAM', 'PBEAML'}
            % Все хорошо
        otherwise
            continue;
    end
    
    % Нулевые офсеты
    [Offset_vector_A, Offset_vector_B] = Get_Bar_Offset_Vectors(DB_ELEMENTS{i}.ID);
    
    if ((~isempty(Offset_vector_A)) && ((Offset_vector_A(1) ~= 0) || (Offset_vector_A(2) ~= 0) || (Offset_vector_A(3) ~= 0)))
        continue;
    end
    if ((~isempty(Offset_vector_B)) && ((Offset_vector_B(1) ~= 0) || (Offset_vector_B(2) ~= 0) || (Offset_vector_B(3) ~= 0)))
        continue;
    end
    
    % Не должно быть Pinned DOFs
    [Pinned_DOFs_A, Pinned_DOFs_B] = Get_Bar_Pinned_DOFs(DB_ELEMENTS{i}.ID);
    
    if (~isempty(Pinned_DOFs_A))
        continue;
    end
    if (~isempty(Pinned_DOFs_B))
        continue;
    end
    
    % Ветвление по типу property set
    switch DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.type

        case 'PBAR'

            % Должно быть I12 = 0
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I12) >= I12_Tolerance)
                continue;
            end
            
            % Должно быть I1 = I2
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I2) >= I1_I2_Relative_Tolerance * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1)
                continue;
            end
            
            % Должно быть J = I1 + I2
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.J - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I2) >= J_I1_I2_Relative_Tolerance * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.J)
                continue;
            end
            
            % Если K1 не пусто, то должно быть K1 = 0.9
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K1)) && (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K1 - 0.9) >= K_Relative_Tolerance * 0.9))
                continue;
            end
            
            % Если K2 не пусто, то должно быть K2 = 0.9
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K2)) && (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K2 - 0.9) >= K_Relative_Tolerance * 0.9))
                continue;
            end
            
            % Должны совпадать радиусы, вычисленные из площади и из моментов инерции
            R_Area = sqrt(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.Area / pi);
            R_I1 = nthroot(4 * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 / pi, 4);
            if (abs(R_Area - R_I1) >= RA_RI_Relative_Tolerance * R_Area)
                continue;
            end
            
            Bolt_Shank_counter = Bolt_Shank_counter + 1;
            Bolt_Shank_elements(Bolt_Shank_counter) = DB_ELEMENTS{i}.ID;

        case 'PBARL'

            % Круглое сечение
            if (~strcmp(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.Shape, 'ROD'))
                continue;
            end
            
            Bolt_Shank_counter = Bolt_Shank_counter + 1;
            Bolt_Shank_elements(Bolt_Shank_counter) = DB_ELEMENTS{i}.ID;

        case 'PBEAM'

            % Нет промежуточных состояний
            if (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N_intermediate_states ~= 0)
                continue;
            end
            
            % Должно быть I12 = 0
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I12) >= I12_Tolerance)
                continue;
            end
            
            % Должно быть I1 = I2
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I2) >= I1_I2_Relative_Tolerance * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1)
                continue;
            end
            
            % Должно быть J = I1 + I2
            if (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.J - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 - DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I2) >= J_I1_I2_Relative_Tolerance * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.J)
                continue;
            end
            
            % Если K1 не пусто, то должно быть K1 = 0.9
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K1)) && (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K1 - 0.9) >= K_Relative_Tolerance * 0.9))
                continue;
            end
            
            % Если K2 не пусто, то должно быть K2 = 0.9
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K2)) && (abs(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.K2 - 0.9) >= K_Relative_Tolerance * 0.9))
                continue;
            end
            
            % Если N1_A не пусто, то должно быть N1_A = 0
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N1_A)) && (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N1_A ~= 0))
                continue;
            end
            
            % Если N2_A не пусто, то должно быть N2_A = 0
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N2_A)) && (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N2_A ~= 0))
                continue;
            end
            
            % Если N1_B не пусто, то должно быть N1_B = 0
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N1_B)) && (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N1_B ~= 0))
                continue;
            end
            
            % Если N2_B не пусто, то должно быть N2_B = 0
            if ((~isempty(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N2_B)) && (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N2_B ~= 0))
                continue;
            end
            
            % Должны совпадать радиусы, вычисленные из площади и из моментов инерции
            R_Area = sqrt(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.Area / pi);
            R_I1 = nthroot(4 * DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.I1 / pi, 4);
            if (abs(R_Area - R_I1) >= RA_RI_Relative_Tolerance * R_Area)
                continue;
            end
            
            Bolt_Shank_counter = Bolt_Shank_counter + 1;
            Bolt_Shank_elements(Bolt_Shank_counter) = DB_ELEMENTS{i}.ID;

        case 'PBEAML'

            % Нет промежуточных состояний
            if (DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.N_intermediate_states ~= 0)
                continue;
            end
            
            % Круглое сечение
            if (~strcmp(DB_PSETS{AT_PSETS(DB_ELEMENTS{i}.Prop_ID)}.Shape, 'ROD'))
                continue;
            end
            
            Bolt_Shank_counter = Bolt_Shank_counter + 1;
            Bolt_Shank_elements(Bolt_Shank_counter) = DB_ELEMENTS{i}.ID;

    end
    
end

% Если Bolt_Shank_counter = 0, то прекращаем выполнение программы
if (Bolt_Shank_counter == 0)
    fprintf (1, '\nВ модели отсутствуют балочные элементы с круглым сечением !\n');
    ErrCode = -1;
    return;
end

% Финализация массива Bolt_Shank_elements
Bolt_Shank_elements = Bolt_Shank_elements(1:Bolt_Shank_counter);

% Локальная адресная таблица для массива Bolt_Shank_elements
Max_Element_Number = 0;

for i=1:Bolt_Shank_counter
    if (Bolt_Shank_elements(i) > Max_Element_Number)
        Max_Element_Number = Bolt_Shank_elements(i);
    end
end

AT_shank_elements = zeros(Max_Element_Number,1);

for i=1:Bolt_Shank_counter

    if (AT_shank_elements(Bolt_Shank_elements(i)) ~= 0)
        fprintf (1, '\nОбнаружен повтор номера элемента %i при создании адресной таблицы элементов круглого сечения !\n', Bolt_Shank_elements(i));
        ErrCode = -1;
        return;
    end

    AT_shank_elements(Bolt_Shank_elements(i)) = i;

end

% Запись диагностической информации
Diagnostic_Data.Circular_Cross_Section_Elements = Bolt_Shank_elements;

fprintf (1, 'OK!\n');

% --- Формирование цепочек балочных элементов ---

fprintf (1, 'Объединение балок в цепочки...          ');

Element_marker_table = zeros(size(Bolt_Shank_elements,1),1);
Bolt_Shank_chains = cell(size(Bolt_Shank_elements,1),1);
Bolt_chains_counter = 0;

Half_Chain_N_items_preallocate = 1000;

% Цикл по балочным элементам круглого сечения
for i=1:size(Bolt_Shank_elements,1)
    
    % Если элемент уже был использован, то переходим к следующему
    if (Element_marker_table(i) == 1)
        continue;
    end
    
    % Отметка в таблице маркеров
    Element_marker_table(i) = 1;
    
    % Создаем временную цепочку из текущего элемента
    tmp_chain.Start_element = Bolt_Shank_elements(i);
    tmp_chain.Start_nodes = zeros(2,1);
    tmp_chain.Start_nodes(1) = DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_elements(i))}.Nodes(1);
    tmp_chain.Start_nodes(2) = DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_elements(i))}.Nodes(2);
    
    tmp_chain.Left_elements = zeros(Half_Chain_N_items_preallocate,1);
    tmp_chain.Right_elements = zeros(Half_Chain_N_items_preallocate,1);
    
    tmp_chain.Left_nodes = zeros(Half_Chain_N_items_preallocate,1);
    tmp_chain.Right_nodes = zeros(Half_Chain_N_items_preallocate,1);
    
    tmp_chain_N_left_elements = 0;
    tmp_chain_N_right_elements = 0;
    
    % Установка флага ошибки при поиске цепочки
    Fail_flag = 0;
    
    % Расширение цепочки влево
        
    Stop_flag = 0;

    % Текущие элемент и узел
    Current_element = tmp_chain.Start_element;
    Current_node = tmp_chain.Start_nodes(1);
    
    % Список элементов с тем же property set, что и у стартового элемента цепочки
    Current_pset_elm_list = AST_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(tmp_chain.Start_element)}.Prop_ID)}.Elm_list;

    while (1)

        if (Stop_flag == 1)
            break;
        end

        % Формирование выборки подходящих балочных элементов, связанных с текущим узлом
        
        Current_node_elm_list = Get_Elements_From_Nodes(Current_node, []);
        Current_node_shanks = intersect(Current_node_elm_list, Bolt_Shank_elements);
        Current_node_shanks = intersect(Current_node_shanks, Current_pset_elm_list);
        
        Connectivity_set_counter = size(Current_node_shanks,1);
        
        if (Connectivity_set_counter > 0)
            
            Connectivity_set = zeros(Connectivity_set_counter,2);
            Connectivity_set(:,1) = Current_node_shanks(:);
            
            for j=1:Connectivity_set_counter
                Connectivity_set(j,2) = AT_shank_elements(Connectivity_set(j,1));
            end
        
        end

        % Обработка исключения
        if (Connectivity_set_counter == 0)
            fprintf (1, '\nНепредвиденная ошибка: отсутствуют балочные элементы, связанные с узлом %i !\n', Current_node);
            ErrCode = -1;
            return;
        end

        % Если найден только один связанный элемент, то прекращаем расширение влево
        if (Connectivity_set_counter == 1)
            Stop_flag = 1;
            continue;
        end

        % Если найдено больше двух связанных элементов, то делаем отметки в таблице маркеров и прекращаем расширение влево
        if (Connectivity_set_counter > 2)
            for j=1:Connectivity_set_counter
                Element_marker_table(Connectivity_set(j,2)) = 1;
            end
            Stop_flag = 1;
            Fail_flag = 1;
            continue;
        end

        % Если найдено ровно два связанных элемента, то устанавливаем, какой из них новый
        if (Connectivity_set(1,1) == Current_element)
            New_item = Connectivity_set(2,:);
        else
            New_item = Connectivity_set(1,:);
        end

        % Если новый элемент уже использовался ранее, то прекращаем расширение влево
        if (Element_marker_table(New_item(1,2)) == 1)
            Stop_flag = 1;
            Fail_flag = 1;
            continue;
        end

        % Делаем отметку в таблице маркеров
        Element_marker_table(New_item(1,2)) = 1;

        % Обновляем текущие элемент и узел
        Current_element = New_item(1,1);
        if (DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(1) == Current_node)
            Current_node = DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(2);
        else
            Current_node = DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(1);
        end

        % Присоединяем новый элемент к цепочке
        tmp_chain_N_left_elements = tmp_chain_N_left_elements + 1;
        
        tmp_chain.Left_elements(tmp_chain_N_left_elements) = Current_element;
        tmp_chain.Left_nodes(tmp_chain_N_left_elements) = Current_node;

    end
    
    % Если цепочка неправильная, то переходим к следующей итерации
    if (Fail_flag == 1)
        continue;
    end
    
    % Расширение цепочки вправо
    
    Stop_flag = 0;
    
    % Текущие элемент и узел
    Current_element = tmp_chain.Start_element;
    Current_node = tmp_chain.Start_nodes(2);
    
    while (1)
        
        if (Stop_flag == 1)
            break;
        end
        
        % Формирование выборки подходящих балочных элементов, связанных с текущим узлом
        
        Current_node_elm_list = Get_Elements_From_Nodes(Current_node, []);
        Current_node_shanks = intersect(Current_node_elm_list, Bolt_Shank_elements);
        Current_node_shanks = intersect(Current_node_shanks, Current_pset_elm_list);
        
        Connectivity_set_counter = size(Current_node_shanks,1);
        
        if (Connectivity_set_counter > 0)
            
            Connectivity_set = zeros(Connectivity_set_counter,2);
            Connectivity_set(:,1) = Current_node_shanks(:);
            
            for j=1:Connectivity_set_counter
                Connectivity_set(j,2) = AT_shank_elements(Connectivity_set(j,1));
            end
        
        end
        
        % Обработка исключения
        if (Connectivity_set_counter == 0)
            fprintf (1, '\nНепредвиденная ошибка: отсутствуют балочные элементы, связанные с узлом %i !\n', Current_node);
            ErrCode = -1;
            return;
        end
        
        % Если найден только один связанный элемент, то прекращаем расширение вправо
        if (Connectivity_set_counter == 1)
            Stop_flag = 1;
            continue;
        end
        
        % Если найдено больше двух связанных элементов, то делаем отметки в таблице маркеров и прекращаем расширение вправо
        if (Connectivity_set_counter > 2)
            for j=1:Connectivity_set_counter
                Element_marker_table(Connectivity_set(j,2)) = 1;
            end
            Stop_flag = 1;
            Fail_flag = 1;
            continue;
        end
        
        % Если найдено ровно два связанных элемента, то устанавливаем, какой из них новый
        if (Connectivity_set(1,1) == Current_element)
            New_item = Connectivity_set(2,:);
        else
            New_item = Connectivity_set(1,:);
        end
        
        % Если новый элемент уже использовался ранее, то прекращаем расширение вправо
        if (Element_marker_table(New_item(1,2)) == 1)
            Stop_flag = 1;
            Fail_flag = 1;
            continue;
        end
 
        % Делаем отметку в таблице маркеров
        Element_marker_table(New_item(1,2)) = 1;
        
        % Обновляем текущие элемент и узел
        Current_element = New_item(1,1);
        if (DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(1) == Current_node)
            Current_node = DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(2);
        else
            Current_node = DB_ELEMENTS{AT_ELEMENTS(Current_element)}.Nodes(1);
        end
        
        % Присоединяем новый элемент к цепочке
        tmp_chain_N_right_elements = tmp_chain_N_right_elements + 1;
        
        tmp_chain.Right_elements(tmp_chain_N_right_elements) = Current_element;
        tmp_chain.Right_nodes(tmp_chain_N_right_elements) = Current_node;
        
    end
    
    % Если цепочка неправильная, то переходим к следующей итерации
    if (Fail_flag == 1)
        continue;
    end
    
    % У всех элементов в цепочке должен быть один и тот же property set
    Start_elm_pset = DB_ELEMENTS{AT_ELEMENTS(tmp_chain.Start_element)}.Prop_ID;
    
    for j=1:tmp_chain_N_left_elements
        if(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(tmp_chain.Left_elements(j))}.Prop_ID)}.ID ~= Start_elm_pset)
            Fail_flag = 1;
            break;
        end
    end
    
    if (Fail_flag == 1)
        continue;
    end
    
    for j=1:tmp_chain_N_right_elements
        if(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(tmp_chain.Right_elements(j))}.Prop_ID)}.ID ~= Start_elm_pset)
            Fail_flag = 1;
            break;
        end
    end
    
    if (Fail_flag == 1)
        continue;
    end
    
    % Добавление текущей цепочки в общий массив
    
    Bolt_chains_counter = Bolt_chains_counter + 1;
    
    Bolt_Shank_chains{Bolt_chains_counter}.N_nodes = 2 + tmp_chain_N_left_elements + tmp_chain_N_right_elements;
    Bolt_Shank_chains{Bolt_chains_counter}.Nodes = zeros(Bolt_Shank_chains{Bolt_chains_counter}.N_nodes,1);
    
    Tmp_counter = 0;
    
    for j=1:tmp_chain_N_left_elements
        Tmp_counter = Tmp_counter + 1;
        Bolt_Shank_chains{Bolt_chains_counter}.Nodes(Tmp_counter) = tmp_chain.Left_nodes(tmp_chain_N_left_elements - j + 1);
    end
    
    Tmp_counter = Tmp_counter + 1;
    Bolt_Shank_chains{Bolt_chains_counter}.Nodes(Tmp_counter) = tmp_chain.Start_nodes(1);
    
    Tmp_counter = Tmp_counter + 1;
    Bolt_Shank_chains{Bolt_chains_counter}.Nodes(Tmp_counter) = tmp_chain.Start_nodes(2);
    
    for j=1:tmp_chain_N_right_elements
        Tmp_counter = Tmp_counter + 1;
        Bolt_Shank_chains{Bolt_chains_counter}.Nodes(Tmp_counter) = tmp_chain.Right_nodes(j);
    end
    
    Bolt_Shank_chains{Bolt_chains_counter}.N_elements = 1 + tmp_chain_N_left_elements + tmp_chain_N_right_elements;
    Bolt_Shank_chains{Bolt_chains_counter}.Elements = zeros(Bolt_Shank_chains{Bolt_chains_counter}.N_elements,1);
    
    Tmp_counter = 0;
    
    for j=1:tmp_chain_N_left_elements
        Tmp_counter = Tmp_counter + 1;
        Bolt_Shank_chains{Bolt_chains_counter}.Elements(Tmp_counter) = tmp_chain.Left_elements(tmp_chain_N_left_elements - j + 1);
    end
    
    Tmp_counter = Tmp_counter + 1;
    Bolt_Shank_chains{Bolt_chains_counter}.Elements(Tmp_counter) = tmp_chain.Start_element;
    
    for j=1:tmp_chain_N_right_elements
        Tmp_counter = Tmp_counter + 1;
        Bolt_Shank_chains{Bolt_chains_counter}.Elements(Tmp_counter) = tmp_chain.Right_elements(j);
    end

end

% Если Bolt_chains_counter = 0, то прекращаем выполнение программы
if (Bolt_chains_counter == 0)
    fprintf (1, '\nОшибка: не удалось построить ни одну цепочку балочных элементов!\n');
    fprintf (1, 'Это означает, что ни одна последовательность балочных элементов круглого сечения с одним и тем же property set\n');
    fprintf (1, 'не образует цепочку, то есть либо разветвляется в каком-то месте, либо образует замкнутое кольцо.\n');
    ErrCode = -1;
    return;
end

% Финализация массива Bolt_Shank_chains
Bolt_Shank_chains = Bolt_Shank_chains(1:Bolt_chains_counter);

% Запись диагностической информации
Diagnostic_Data.Recognized_Chains = Bolt_Shank_chains;

fprintf (1, 'OK!\n');

% Описание массива:
% Bolt_Shank_chains{i}
%   .N_nodes
%   .Nodes(N_nodes,1)
%   .N_elements
%   .Elements(N_elements,1)

% --- Создание таблицы маркеров для отбраковки неподходящих цепочек ---

Chain_screening_marker_table = zeros(size(Bolt_Shank_chains,1),1);
Chain_screening_reason_table = cell(size(Bolt_Shank_chains,1),1);

% --- Проверка длин элементов ---

fprintf (1, 'Проверка длин элементов...              ');

for i=1:size(Bolt_Shank_chains,1)
    
    % Если цепочка из одного элемента, то пропускаем ее
    if (Bolt_Shank_chains{i}.N_elements == 1)
        continue;
    end
    
    % Если цепочка из двух элементов, не допускаются оба нулевой длины
    if (Bolt_Shank_chains{i}.N_elements == 2)
        if (   Equiv_Check_Cube(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(1))}.Nodes(1), DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(1))}.Nodes(2)) ...
            && Equiv_Check_Cube(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(2))}.Nodes(1), DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(2))}.Nodes(2)))
            
            Chain_screening_marker_table(i) = 1;
            Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка длин балочных элементов в стержне болта.\n') ...
                                               sprintf('Объяснение:\n') ...
                                               sprintf('Исходный балочный элемент входит в цепочку, состоящую из двух элементов.\n') ...
                                               sprintf('В этом случае должно быть выполнено следующее условие:\n') ...
                                               sprintf('не допускается, чтобы оба элемента в цепочке имели нулевую длину.\n') ...
                                               sprintf('(Принимается, что балочный элемент имеет нулевую длину, если расстояние между его узлами меньше текущего заданного допуска %e).', Global_equivalence_tolerance)];
            continue;
            
        end
    end
    
    % Если цепочка из 4 и более элементов, то со второго по предпоследний не должно быть двух подряд идущих элементов нулевой длины
    if (Bolt_Shank_chains{i}.N_elements >= 4)
        
        Allow_next_zero_length = 1;
        
        % Цикл со второго по предпоследний элементы
        for j=2:Bolt_Shank_chains{i}.N_elements-1
            
            Current_zero_length_flag = Equiv_Check_Cube(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(1), DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(2));
            
            if ((Allow_next_zero_length == 0) && (Current_zero_length_flag == 1))
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка длин балочных элементов в стержне болта.\n') ...
                                                   sprintf('Объяснение:\n') ...
                                                   sprintf('Исходный балочный элемент входит в цепочку, состоящую из 4 или более элементов.\n') ...
                                                   sprintf('В этом случае должно быть выполнено следующее условие:\n') ...
                                                   sprintf('со второго по предпоследний элементы в цепочке не должно быть двух подряд идущих элементов нулевой длины.\n') ...
                                                   sprintf('(Принимается, что балочный элемент имеет нулевую длину, если расстояние между его узлами меньше текущего заданного допуска %e).', Global_equivalence_tolerance)];
                break;
            end
            
            if (Current_zero_length_flag == 1)
                Allow_next_zero_length = 0;
            else
                Allow_next_zero_length = 1;
            end
            
        end
        
        continue;
        
    end
    
end

fprintf (1, 'OK!\n');

% --- Проверка прямолинейности цепочек ---

fprintf (1, 'Проверка прямолинейности цепочек...     ');

for i=1:size(Bolt_Shank_chains,1)
    
    % Если цепочка уже отбракована, то пропускаем ее
    if (Chain_screening_marker_table(i) == 1)
        continue;
    end
    
    % Если цепочка из одного элемента, то пропускаем ее
    if (Bolt_Shank_chains{i}.N_elements == 1)
        continue;
    end
    
    % Цепочка из более чем одного элемента
    Reference_element = 0;
    for j=1:Bolt_Shank_chains{i}.N_elements
        
        if ((Reference_element == 0) && (~Equiv_Check_Cube(Bolt_Shank_chains{i}.Nodes(j), Bolt_Shank_chains{i}.Nodes(j+1))))
            Reference_element = Bolt_Shank_chains{i}.Elements(j);
            Reference_vector = DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(j+1))}.Location - DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(j))}.Location;
            continue;
        end
        
        if ((Reference_element ~= 0) && (~Equiv_Check_Cube(Bolt_Shank_chains{i}.Nodes(j), Bolt_Shank_chains{i}.Nodes(j+1))))
            
            Current_vector = DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(j+1))}.Location - DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(j))}.Location;
            
            Angle = acosd(dot(Reference_vector, Current_vector) / (norm(Reference_vector) * norm(Current_vector)));
            
            if (Angle > Bolt_shank_deg_tolerance)
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка прямолинейности цепочки балочных элементов в стержне болта.\n') ...
                                                   sprintf('Угол между некоторыми балочными элементами в цепочке составляет %f%s,\n', Angle, char(176)) ...
                                                   sprintf('в то время как максимальный допускаемый угол излома составляет %f%s.', Bolt_shank_deg_tolerance, char(176))];
                break;
            end
            
        end
        
    end
    
end

fprintf (1, 'OK!\n');

% --- Проверка изолированности цепочек ---

fprintf (1, 'Проверка изолированности цепочек...     ');

for i=1:size(Bolt_Shank_chains,1)
    
    % Если цепочка уже отбракована, то пропускаем ее
    if (Chain_screening_marker_table(i) == 1)
        continue;
    end
    
    for j=1:Bolt_Shank_chains{i}.N_elements
        
        Elm_node_1 = Get_Elements_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(1), []);
        Elm_node_2 = Get_Elements_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(2), []);
        
        Solid_Elm_node_1 = Get_Solids_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(1), []);
        Solid_Elm_node_2 = Get_Solids_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(2), []);
        
        Common_elements = intersect(Elm_node_1, Elm_node_2);
        Common_solid_elements = intersect(Solid_Elm_node_1, Solid_Elm_node_2);
        
        Common_elements = setdiff(Common_elements, Common_solid_elements);
        
        if (isempty(Common_elements))
            fprintf (1, '\nНепредвиденная ошибка: не найдено общих элементов у узлов %i, %i !\n', DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(1), DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(2));
            fprintf (1, 'Обратитесь к разработчику для решения этого вопроса.\n');
            ErrCode = -1;
            return;
        end
        
        if (size(Common_elements,1) ~= 1)
            Chain_screening_marker_table(i) = 1;
            Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка изолированности балочных элементов в стержне болта (проверка на посторонние элементы между узлами).\n') ...
                                               sprintf('Это означает, что в стержне болта есть балочный элемент, оба узла которого принадлежат еще какому-то конечному элементу.\n') ...
                                               sprintf('Между узлами каждого балочного элемента в стержне болта не должно располагаться никаких иных конечных элементов\n') ...
                                               sprintf('(исключение могут составлять только твердотельные элементы, явно моделирующие соты между обшивками сэндвича).')];
            break;
        end
        
        MPC_node_1 = Get_MPCs_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(1), []);
        MPC_node_2 = Get_MPCs_From_Nodes(DB_ELEMENTS{AT_ELEMENTS(Bolt_Shank_chains{i}.Elements(j))}.Nodes(2), []);
        
        Common_MPC = intersect(MPC_node_1, MPC_node_2);
        
        if (~isempty(Common_MPC))
            Chain_screening_marker_table(i) = 1;
            Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка изолированности балочных элементов в стержне болта (проверка на MPC между узлами).\n') ...
                                               sprintf('Это означает, что в стержне болта есть балочный элемент, оба узла которого принадлежат некоторому MPC.\n') ...
                                               sprintf('Между узлами каждого балочного элемента в стержне болта не должно располагаться никаких MPC.')];
            break;
        end
        
    end

end

fprintf (1, 'OK!\n');

% --- Проверка топологии крепежа ---

fprintf (1, 'Проверка топологии крепежа...           ');

Bolt_topology = cell(size(Bolt_Shank_chains,1),1);
Bolt_Offset_MPC = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_nodes = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_topology = cell(size(Bolt_Shank_chains,1),1);
Bolt_Midsurface_nodes = cell(size(Bolt_Shank_chains,1),1);
Bolt_RBAR_MPCs = cell(size(Bolt_Shank_chains,1),1);
Bolt_Head_MPC_Index = cell(size(Bolt_Shank_chains,1),1);
Bolt_Bush_elements = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_mesh_MPC = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_mesh_elm = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_mesh_BS_Elm = cell(size(Bolt_Shank_chains,1),1);
Bolt_Joint_mesh_BS_Conn_Nodes = cell(size(Bolt_Shank_chains,1),1);

for i=1:size(Bolt_Shank_chains,1)
    
    % Если цепочка уже отбракована, то пропускаем ее
    if (Chain_screening_marker_table(i) == 1)
        continue;
    end
    
    if (Bolt_Shank_chains{i}.N_nodes >= 4)
        
        % Потенциальный болт Рутмана
        Bar_fastener_flag = 0;
        
        % Цикл по промежуточным узлам
        for j=2:Bolt_Shank_chains{i}.N_nodes-1
            
            % Должен быть один или несколько CBUSH
            CBUSH_list = Get_CBUSHs_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), []);
            if (size(CBUSH_list,1) < 1)
                Bar_fastener_flag = 1;
                break;
            end
            
            % Не должно быть MPC
            if (~isempty(Get_MPCs_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), [])))
                Bar_fastener_flag = 1;
                break;
            end
            
            % Должно быть 2 элемента, кроме CBUSH
            if (size(Get_Elements_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), CBUSH_list),1) ~= 2)
                Bar_fastener_flag = 1;
                break;
            end
            
        end
        
        % У первого узла в цепочке должен быть один элемент
        if (Bar_fastener_flag == 0)
            if (size(Get_Elements_From_Nodes(Bolt_Shank_chains{i}.Nodes(1), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
        % У первого узла в цепочке должен быть один MPC
        if (Bar_fastener_flag == 0)
            if (size(Get_MPCs_From_Nodes(Bolt_Shank_chains{i}.Nodes(1), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
        % У первого узла в цепочке MPC должен быть типа RBAR
        if (Bar_fastener_flag == 0)
            if (size(Get_RBARs_From_Nodes(Bolt_Shank_chains{i}.Nodes(1), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
        % У последнего узла в цепочке должен быть один элемент
        if (Bar_fastener_flag == 0)
            if (size(Get_Elements_From_Nodes(Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
        % У последнего узла в цепочке должен быть один MPC
        if (Bar_fastener_flag == 0)
            if (size(Get_MPCs_From_Nodes(Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
        % У последнего узла в цепочке MPC должен быть типа RBAR
        if (Bar_fastener_flag == 0)
            if (size(Get_RBARs_From_Nodes(Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes), []),1) ~= 1)
                Bar_fastener_flag = 1;
            end
        end
        
    else
        
        % Балочный болт
        Bar_fastener_flag = 1;
        
    end
    
    switch Bar_fastener_flag
        
        case (0)
            
            Bolt_topology{i} = 'rutman_fastener';
            
            % Выделение памяти
            
            Bolt_Midsurface_nodes{i} = zeros(Bolt_Shank_chains{i}.N_nodes-2,1);
            Bolt_RBAR_MPCs{i} = zeros(Bolt_Shank_chains{i}.N_nodes-1,1);
            Bolt_Bush_elements{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            
            Bolt_Offset_MPC{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            Bolt_Joint_nodes{i} = zeros(Bolt_Shank_chains{i}.N_nodes-2,1);
            Bolt_Joint_topology{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            
            Bolt_Joint_mesh_MPC{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            Bolt_Joint_mesh_elm{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            
            Bolt_Joint_mesh_BS_Elm{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            Bolt_Joint_mesh_BS_Conn_Nodes{i} = cell(Bolt_Shank_chains{i}.N_nodes-2,1);
            
            % Заполнение Bolt_Bush_elements и Bolt_Midsurface_nodes
            
            Fail_flag = 0;
            
            for j=2:Bolt_Shank_chains{i}.N_nodes-1
                
                % Получение списка CBUSH
                CBUSH_list = Get_CBUSHs_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), []);
                Bolt_Bush_elements{i}{j-1} = CBUSH_list;
                
                % Проверка CBUSH элементов на второй общий узел
                if (size(CBUSH_list, 1) > 1)
                    
                    if (DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(1))}.Nodes(1) == Bolt_Shank_chains{i}.Nodes(j))
                        Ref_Node = DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(1))}.Nodes(2);
                    else
                        Ref_Node = DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(1))}.Nodes(1);
                    end
                    
                    for k=1:size(CBUSH_list, 1)
                        
                        if (DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(k))}.Nodes(1) == Bolt_Shank_chains{i}.Nodes(j))
                            Current_Node = DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(k))}.Nodes(2);
                        else
                            Current_Node = DB_ELEMENTS{AT_ELEMENTS(CBUSH_list(k))}.Nodes(1);
                        end
                        
                        if (Current_Node ~= Ref_Node)
                            Fail_flag = 1;
                            Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка параллельно работающих CBUSH на второй общий узел).\n') ...
                                                               sprintf('Объяснение:\n') ...
                                                               sprintf('Данный элемент крепежа потенциально является болтом Рутмана с несколькими параллельно работающими в одной точке смятия CBUSH.\n') ...
                                                               sprintf('В таком случае все CBUSH должны располагаться между узлом стержня болта и единственным узлом сетки.\n') ...
                                                               sprintf('В данной ситуации параллельно работающие CBUSH ведут от одного узла стержня болта к двум или более узлам сетки.')];
                            break;
                        end
                        
                    end
                    
                end
                
                if (Fail_flag == 1)
                    break;
                end
                
                % Заполнение Bolt_Midsurface_nodes
                if (DB_ELEMENTS{AT_ELEMENTS(Bolt_Bush_elements{i}{j-1}(1))}.Nodes(1) == Bolt_Shank_chains{i}.Nodes(j))
                    Bolt_Midsurface_nodes{i}(j-1) = DB_ELEMENTS{AT_ELEMENTS(Bolt_Bush_elements{i}{j-1}(1))}.Nodes(2);
                else
                    Bolt_Midsurface_nodes{i}(j-1) = DB_ELEMENTS{AT_ELEMENTS(Bolt_Bush_elements{i}{j-1}(1))}.Nodes(1);
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
            % Заполнение первого и последнего RBAR
            
            Bolt_RBAR_MPCs{i}(1) = Get_RBARs_From_Nodes(Bolt_Shank_chains{i}.Nodes(1), []);
            Bolt_RBAR_MPCs{i}(Bolt_Shank_chains{i}.N_nodes-1) = Get_RBARs_From_Nodes(Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes), []);
            
            % Проверка первого RBAR и первого CBUSH на общий узел
            
            if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(1))}.ind_grid == Bolt_Shank_chains{i}.Nodes(1))
                RBAR_Node = DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(1))}.dep_grid;
            else
                RBAR_Node = DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(1))}.ind_grid;
            end
            
            CBUSH_Node = Bolt_Midsurface_nodes{i}(1);
            
            if (RBAR_Node ~= CBUSH_Node)
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка первого RBAR и первого CBUSH на общий узел).\n') ...
                                                   sprintf('Объяснение:\n') ...
                                                   sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                   sprintf('В этом случае согласно топологии крепежа Рутмана первый RBAR и первый CBUSH элемент должны иметь общий узел.\n') ...
                                                   sprintf('В данной ситуации первый RBAR и первый CBUSH элемент не имеют общих узлов.')];
                continue;
            end
            
            % Проверка последнего RBAR и последнего CBUSH на общий узел
            
            if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(Bolt_Shank_chains{i}.N_nodes-1))}.ind_grid == Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes))
                RBAR_Node = DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(Bolt_Shank_chains{i}.N_nodes-1))}.dep_grid;
            else
                RBAR_Node = DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(Bolt_Shank_chains{i}.N_nodes-1))}.ind_grid;
            end
            
            CBUSH_Node = Bolt_Midsurface_nodes{i}(Bolt_Shank_chains{i}.N_nodes-2);
            
            if (RBAR_Node ~= CBUSH_Node)
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка последнего RBAR и последнего CBUSH на общий узел).\n') ...
                                                   sprintf('Объяснение:\n') ...
                                                   sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                   sprintf('В этом случае согласно топологии крепежа Рутмана последний RBAR и последний CBUSH элемент должны иметь общий узел.\n') ...
                                                   sprintf('В данной ситуации последний RBAR и последний CBUSH элемент не имеют общих узлов.')];
                continue;
            end
            
            % Поиск промежуточных RBAR
            
            Fail_flag = 0;
            
            for j=1:Bolt_Shank_chains{i}.N_nodes-3
                
                % Поиск промежуточного RBAR
                
                RBAR_node_1 = Get_RBARs_From_Nodes(Bolt_Midsurface_nodes{i}(j), []);
                RBAR_node_2 = Get_RBARs_From_Nodes(Bolt_Midsurface_nodes{i}(j+1), []);
                
                Common_RBARs = intersect(RBAR_node_1, RBAR_node_2);
                
                if (isempty(Common_RBARs))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка наличия RBAR между соседними ярусами сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между соседними ярусами сетки должен располагаться один RBAR.\n') ...
                                                       sprintf('В данной ситуации существует два соседних яруса сетки, между которыми нет RBAR.')];
                    break;
                end
                
                if (size(Common_RBARs,1) ~= 1)
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка числа RBAR между соседними ярусами сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между соседними ярусами сетки должен располагаться один RBAR.\n') ...
                                                       sprintf('В данной ситуации существует два соседних яруса сетки, между которыми располагается более, чем один RBAR.')];
                    break;
                end
                
                Bolt_RBAR_MPCs{i}(j+1) = Common_RBARs(1);
                
                % Проверка того, что между узлами нет других MPC
                
                MPC_node_1 = Get_MPCs_From_Nodes(Bolt_Midsurface_nodes{i}(j), []);
                MPC_node_2 = Get_MPCs_From_Nodes(Bolt_Midsurface_nodes{i}(j+1), []);
                
                Common_MPC = intersect(MPC_node_1, MPC_node_2);
                
                if (isempty(Common_MPC))
                    fprintf (1, '\nНепредвиденная ошибка: не найдено общих MPC у узлов %i, %i !\n', Bolt_Midsurface_nodes{i}(j), Bolt_Midsurface_nodes{i}(j+1));
                    ErrCode = -1;
                    return;
                end
                
                if (size(Common_MPC,1) ~= 1)
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка на посторонние MPC между узлами RBAR).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между узлами каждого RBAR не должно располагатсья иных MPC, кроме этого RBAR.\n') ...
                                                       sprintf('В данной ситуации существует RBAR, между которыми которого существует посторонний MPC.')];
                    break;
                end
                
                % Проверка того, что между узлами нет элементов
                
                Elm_node_1 = Get_Elements_From_Nodes(Bolt_Midsurface_nodes{i}(j), []);
                Elm_node_2 = Get_Elements_From_Nodes(Bolt_Midsurface_nodes{i}(j+1), []);
                
                Solid_Elm_node_1 = Get_Solids_From_Nodes(Bolt_Midsurface_nodes{i}(j), []);
                Solid_Elm_node_2 = Get_Solids_From_Nodes(Bolt_Midsurface_nodes{i}(j+1), []);
                
                Common_elements = intersect(Elm_node_1, Elm_node_2);
                Common_solid_elements = intersect(Solid_Elm_node_1, Solid_Elm_node_2);
                
                Common_elements = setdiff(Common_elements, Common_solid_elements);
                
                if (~isempty(Common_elements))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка на посторонние элементы между узлами RBAR).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между узлами каждого RBAR не должно располагаться никаких конечных элементов\n') ...
                                                       sprintf('(исключение могут составлять только твердотельные элементы, явно моделирующие соты между обшивками сэндвича).\n') ...
                                                       sprintf('В данной ситуации существует RBAR, оба узла которого принадлежат некоторому постороннему конечному элементу.')];
                    break;
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
            % Определение Bolt_Head_MPC_Index
            
            if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(1))}.ind_grid == Bolt_Shank_chains{i}.Nodes(1))
                Head_flag_1 = 1;
            else
                Head_flag_1 = 0;
            end
            
            if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(Bolt_Shank_chains{i}.N_nodes-1))}.ind_grid == Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes))
                Head_flag_2 = 1;
            else
                Head_flag_2 = 0;
            end
            
            if ((Head_flag_1 + Head_flag_2 == 0) || (Head_flag_1 + Head_flag_2 == 2))
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка на независимый узел RBAR в головке болта).\n') ...
                                                   sprintf('Объяснение:\n') ...
                                                   sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                   sprintf('В этом случае согласно топологии крепежа Рутмана один из концевых узлов стержня болта должен являться независимым узлом соответствующего RBAR,\n') ...
                                                   sprintf('в то время как другой концевой узел стержня болта должен являться зависимым узлом соответствующего RBAR.\n') ...
                                                   sprintf('В данной ситуации оба концевых узла стержня болта являются одновременно либо независимыми, либо зависимыми узлами соответствующих RBAR.')];
                continue;
            end
            
            if (Head_flag_1 == 1)
                Bolt_Head_MPC_Index{i} = 1;
            else
                Bolt_Head_MPC_Index{i} = Bolt_Shank_chains{i}.N_nodes-1;
            end
            
            % Проверка согласованности зависимых и независимых узлов в цепочке RBAR
            
            Fail_flag = 0;
            
            if (Bolt_Head_MPC_Index{i} == 1)
                
                for j=1:Bolt_Shank_chains{i}.N_nodes-2
                    
                    if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(j+1))}.ind_grid ~= Bolt_Midsurface_nodes{i}(j))
                        Fail_flag = 1;
                        break;
                    end

                end
                
            else
                
                for j=1:Bolt_Shank_chains{i}.N_nodes-2
                    
                    if (DB_MPC{AT_MPC(Bolt_RBAR_MPCs{i}(j+1))}.dep_grid ~= Bolt_Midsurface_nodes{i}(j))
                        Fail_flag = 1;
                        break;
                    end

                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка согласованности зависимых и независимых узлов RBAR).\n') ...
                                                   sprintf('Объяснение:\n') ...
                                                   sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                   sprintf('В этом случае согласно топологии крепежа Рутмана цепочка RBAR должна быть такой, что\n') ...
                                                   sprintf('в ней каждый зависимый узел предыдущего RBAR является независимым узлом следующего RBAR.\n') ...
                                                   sprintf('В данной ситуации существует узел, который одновременно для двух RBAR является либо зависимым, либо независимым узлом.')];
                continue;
            end
            
            % Поиск MPC для офсетов
            
            for j=1:Bolt_Shank_chains{i}.N_nodes-2
                
                Excluded_MPC = [Bolt_RBAR_MPCs{i}(j); Bolt_RBAR_MPCs{i}(j+1)];
                
                MPC_list = Get_MPCs_From_Nodes(Bolt_Midsurface_nodes{i}(j), Excluded_MPC);
                
                Offset_MPC_find_flag = 0;
                
                if (~isempty(MPC_list))
                    if(size(MPC_list,1) == 1)
                        if (strcmp(DB_MPC{AT_MPC(MPC_list(1))}.type, 'RBE2'))
                            if (size(DB_MPC{AT_MPC(MPC_list(1))}.dep_grid,1) == 1)
                                if (Compare_DOFs(DB_MPC{AT_MPC(MPC_list(1,1))}.dep_DOF, 123456) == 1)
                                    
                                    Excluded_Elements = Bolt_Bush_elements{i}{j};
                                    Element_list = Get_Elements_From_Nodes(Bolt_Midsurface_nodes{i}(j), Excluded_Elements);
                                    
                                    if (isempty(Element_list))
                                    
                                        Offset_MPC_find_flag = 1;
                                        Bolt_Offset_MPC{i}{j} = MPC_list(1);

                                        if (DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.ind_grid == Bolt_Midsurface_nodes{i}(j))
                                            Bolt_Joint_nodes{i}(j) = DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.dep_grid(1);
                                        else
                                            Bolt_Joint_nodes{i}(j) = DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.ind_grid;
                                        end
                                    
                                    end
                                    
                                end
                            end
                        end
                    end
                end
                
                if (Offset_MPC_find_flag == 0)
                    Bolt_Joint_nodes{i}(j) = Bolt_Midsurface_nodes{i}(j);
                end
                
            end
            
            % Проверка изолированности соседних узлов Bolt_Joint_nodes
            
            Fail_flag = 0;
            
            for j=1:Bolt_Shank_chains{i}.N_nodes-3
                
                % Проверка того, что между узлами нет элементов
                
                Elm_node_1 = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j), []);
                Elm_node_2 = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j+1), []);
                
                Solid_Elm_node_1 = Get_Solids_From_Nodes(Bolt_Joint_nodes{i}(j), []);
                Solid_Elm_node_2 = Get_Solids_From_Nodes(Bolt_Joint_nodes{i}(j+1), []);
                
                Common_elements = intersect(Elm_node_1, Elm_node_2);
                Common_solid_elements = intersect(Solid_Elm_node_1, Solid_Elm_node_2);
                
                Common_elements = setdiff(Common_elements, Common_solid_elements);
                
                if (~isempty(Common_elements))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка на посторонние элементы между узлами двух соседних ярусов присоединенной к болту сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между узлами двух соседних ярусов присоединенной к болту сетки не должно располагаться никаких конечных элементов\n') ...
                                                       sprintf('(исключение могут составлять только твердотельные элементы, явно моделирующие соты между обшивками сэндвича).\n') ...
                                                       sprintf('В данной ситуации существуют узлы двух соседних ярусов присоединенной к болту сетки, которые принадлежат некоторому постороннему конечному элементу.')];
                    break;
                end
                
                % Проверка того, что между узлами нет MPC
                
                MPC_node_1 = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j), Bolt_RBAR_MPCs{i});
                MPC_node_2 = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j+1), Bolt_RBAR_MPCs{i});
                
                Common_MPC = intersect(MPC_node_1, MPC_node_2);
                
                if (~isempty(Common_MPC))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии крепежа Рутмана (проверка на посторонние MPC между узлами двух соседних ярусов присоединенной к болту сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является болтом Рутмана.\n') ...
                                                       sprintf('В этом случае согласно топологии крепежа Рутмана между узлами двух соседних ярусов присоединенной к болту сетки не должно располагатсья никаких MPC, кроме, возможно, RBAR, входящего в болт Рутмана.\n') ...
                                                       sprintf('В данной ситуации существуют узлы двух соседних ярусов присоединенной к болту сетки, между которыми существует посторонний MPC.')];
                    break;
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
            % Поиск присоединенной топологии
            
            Fail_flag = 0;
            
            for j=1:Bolt_Shank_chains{i}.N_nodes-2
                
                if (isempty(Bolt_Offset_MPC{i}{j}))
                    Excluded_Elements = Bolt_Bush_elements{i}{j};
                    Excluded_MPC = [Bolt_RBAR_MPCs{i}(j); Bolt_RBAR_MPCs{i}(j+1)];
                else
                    Excluded_Elements = [];
                    Excluded_MPC = Bolt_Offset_MPC{i}{j};
                end
                
                Element_list = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j), Excluded_Elements);
                MPC_list = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j), Excluded_MPC);
                
                if (isempty(Element_list) && isempty(MPC_list))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка наличия окружающей болт Рутмана сетки.\n') ...
                                                       sprintf('Это означает, что на одном из соединяемых болтом Рутмана ярусов отсутствует окружающая болт сетка.\n') ...
                                                       sprintf('Вместе с каждым болтом на каждом соединяемом ярусе в bdf файле должен быть иметься кусок сетки, окружающий болт со всех сторон.\n') ...
                                                       sprintf('Требуется хотя бы один ряд конечных элементов вокруг болта.')];
                    break;
                end
                
                % Распознавание присоединенной топологии
                
                [JT_type, Connected_elements, Bar_Spider_Elements, Bar_Spider_Connected_Nodes, Err_Message] = Recognize_Joint_Topology(Bolt_Joint_nodes{i}(j), Element_list, MPC_list);
                
                if (isempty(JT_type))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не распознана окружающая болт Рутмана сетка на ярусе %i.\n', j) Err_Message];
                    break;
                else
                    Bolt_Joint_topology{i}{j} = JT_type;
                    Bolt_Joint_mesh_MPC{i}{j} = MPC_list; % В предположении, что поддерживатся присоединенная топология только с одним MPC
                    Bolt_Joint_mesh_elm{i}{j} = Connected_elements;
                    Bolt_Joint_mesh_BS_Elm{i}{j} = Bar_Spider_Elements;
                    Bolt_Joint_mesh_BS_Conn_Nodes{i}{j} = Bar_Spider_Connected_Nodes;
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
        case (1)
            
            Bolt_topology{i} = 'bar_fastener';
            
            % Выделение памяти

            Bolt_Offset_MPC{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            Bolt_Joint_nodes{i} = zeros(Bolt_Shank_chains{i}.N_nodes,1);
            Bolt_Joint_topology{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            
            Bolt_Joint_mesh_MPC{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            Bolt_Joint_mesh_elm{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            
            Bolt_Joint_mesh_BS_Elm{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            Bolt_Joint_mesh_BS_Conn_Nodes{i} = cell(Bolt_Shank_chains{i}.N_nodes,1);
            
            % Поиск MPC для офсетов
            
            for j=1:Bolt_Shank_chains{i}.N_nodes
                
                MPC_list = Get_MPCs_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), []);
                
                Offset_MPC_find_flag = 0;
                
                if (~isempty(MPC_list))
                    if(size(MPC_list,1) == 1)
                        if (strcmp(DB_MPC{AT_MPC(MPC_list(1))}.type, 'RBE2'))
                            if (size(DB_MPC{AT_MPC(MPC_list(1))}.dep_grid,1) == 1)
                                if (Compare_DOFs(DB_MPC{AT_MPC(MPC_list(1,1))}.dep_DOF, 123456) == 1)
                                    
                                    switch j
                                        case (1)
                                            Excluded_Elements = Bolt_Shank_chains{i}.Elements(1);
                                        case Bolt_Shank_chains{i}.N_nodes
                                            Excluded_Elements = Bolt_Shank_chains{i}.Elements(Bolt_Shank_chains{i}.N_elements);
                                        otherwise
                                            Excluded_Elements = [Bolt_Shank_chains{i}.Elements(j-1); Bolt_Shank_chains{i}.Elements(j)];
                                    end
                                    
                                    Element_list = Get_Elements_From_Nodes(Bolt_Shank_chains{i}.Nodes(j), Excluded_Elements);
                                    
                                    if (isempty(Element_list))
                                    
                                        Offset_MPC_find_flag = 1;
                                        Bolt_Offset_MPC{i}{j} = MPC_list(1);

                                        if (DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.ind_grid == Bolt_Shank_chains{i}.Nodes(j))
                                            Bolt_Joint_nodes{i}(j) = DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.dep_grid(1);
                                        else
                                            Bolt_Joint_nodes{i}(j) = DB_MPC{AT_MPC(Bolt_Offset_MPC{i}{j})}.ind_grid;
                                        end
                                    
                                    end
                                    
                                end
                            end
                        end
                    end
                end
                
                if (Offset_MPC_find_flag == 0)
                    Bolt_Joint_nodes{i}(j) = Bolt_Shank_chains{i}.Nodes(j);
                end
                
            end
            
            % Проверка изолированности соседних узлов Bolt_Joint_nodes
            
            Fail_flag = 0;
            
            for j=1:Bolt_Shank_chains{i}.N_nodes-1
                
                % Проверка того, что между узлами нет элементов
                
                Elm_node_1 = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j), Bolt_Shank_chains{i}.Elements);
                Elm_node_2 = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j+1), Bolt_Shank_chains{i}.Elements);
                
                Solid_Elm_node_1 = Get_Solids_From_Nodes(Bolt_Joint_nodes{i}(j), []);
                Solid_Elm_node_2 = Get_Solids_From_Nodes(Bolt_Joint_nodes{i}(j+1), []);
                
                Common_elements = intersect(Elm_node_1, Elm_node_2);
                Common_solid_elements = intersect(Solid_Elm_node_1, Solid_Elm_node_2);
                
                Common_elements = setdiff(Common_elements, Common_solid_elements);
                
                if (~isempty(Common_elements))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии балочного крепежа (проверка на посторонние элементы между узлами двух соседних ярусов присоединенной к болту сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является балочным болтом.\n') ...
                                                       sprintf('В этом случае между узлами двух соседних ярусов присоединенной к болту сетки не должно располагаться никаких конечных элементов, кроме, возможно, элементов стержня болта\n') ...
                                                       sprintf('(исключение могут составлять только твердотельные элементы, явно моделирующие соты между обшивками сэндвича).\n') ...
                                                       sprintf('В данной ситуации существуют узлы двух соседних ярусов присоединенной к болту сетки, которые принадлежат некоторому постороннему конечному элементу.')];
                    break;
                end
                
                % Проверка того, что между узлами нет MPC
                
                MPC_node_1 = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j), []);
                MPC_node_2 = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j+1), []);
                
                Common_MPC = intersect(MPC_node_1, MPC_node_2);
                
                if (~isempty(Common_MPC))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка топологии балочного крепежа (проверка на посторонние MPC между узлами двух соседних ярусов присоединенной к болту сетки).\n') ...
                                                       sprintf('Объяснение:\n') ...
                                                       sprintf('Данный элемент крепежа потенциально является балочным болтом.\n') ...
                                                       sprintf('В этом случае между узлами двух соседних ярусов присоединенной к болту сетки не должно располагатсья никаких MPC.\n') ...
                                                       sprintf('В данной ситуации существуют узлы двух соседних ярусов присоединенной к болту сетки, между которыми существует посторонний MPC.')];
                    break;
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
            % Поиск присоединенной топологии
            
            Fail_flag = 0;
            
            for j=1:Bolt_Shank_chains{i}.N_nodes
                
                if (isempty(Bolt_Offset_MPC{i}{j}))
                    
                    switch j
                        case (1)
                            Excluded_Elements = Bolt_Shank_chains{i}.Elements(1);
                        case Bolt_Shank_chains{i}.N_nodes
                            Excluded_Elements = Bolt_Shank_chains{i}.Elements(Bolt_Shank_chains{i}.N_elements);
                        otherwise
                            Excluded_Elements = [Bolt_Shank_chains{i}.Elements(j-1); Bolt_Shank_chains{i}.Elements(j)];
                    end
                    
                    Excluded_MPC = [];
                    
                else
                    Excluded_Elements = [];
                    Excluded_MPC = Bolt_Offset_MPC{i}{j};
                end
                
                Element_list = Get_Elements_From_Nodes(Bolt_Joint_nodes{i}(j), Excluded_Elements);
                MPC_list = Get_MPCs_From_Nodes(Bolt_Joint_nodes{i}(j), Excluded_MPC);
                
                if (isempty(Element_list) && isempty(MPC_list))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка наличия окружающей балочный болт сетки.\n') ...
                                                       sprintf('Это означает, что на одном из соединяемых балочным болтом ярусов отсутствует окружающая болт сетка.\n') ...
                                                       sprintf('Вместе с каждым болтом на каждом соединяемом ярусе в bdf файле должен быть иметься кусок сетки, окружающий болт со всех сторон.\n') ...
                                                       sprintf('Требуется хотя бы один ряд конечных элементов вокруг болта.\n') ...
                                                       sprintf('---> Примечание: данный элемент крепежа был определен как балочный болт, и программа считает, что возникли проблемы с окружающей его сеткой.\n') ...
                                                       sprintf('Если же это должен быть болт Рутмана, то тогда проблема не в окружающей его сетке, а в топологии самого болта.\n') ...
                                                       sprintf('Либо она имеет неподдерживаемый в данной версии программы тип (см. руководство пользователя), либо нарушена и требует детальной проверки.')];
                    break;
                end
                
                % Распознавание присоединенной топологии
                
                [JT_type, Connected_elements, Bar_Spider_Elements, Bar_Spider_Connected_Nodes, Err_Message] = Recognize_Joint_Topology(Bolt_Joint_nodes{i}(j), Element_list, MPC_list);
                
                if (isempty(JT_type))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не распознана окружающая балочный болт сетка на ярусе %i.\n', j) Err_Message sprintf('\n') ...
                                                       sprintf('---> Примечание: данный элемент крепежа был определен как балочный болт, и программа считает, что возникли проблемы с окружающей его сеткой.\n') ...
                                                       sprintf('Если же это должен быть болт Рутмана, то тогда проблема не в окружающей его сетке, а в топологии самого болта.\n') ...
                                                       sprintf('Либо она имеет неподдерживаемый в данной версии программы тип (см. руководство пользователя), либо нарушена и требует детальной проверки.')]; %#ok<SPRINTFN>
                    break;
                else
                    Bolt_Joint_topology{i}{j} = JT_type;
                    Bolt_Joint_mesh_MPC{i}{j} = MPC_list; % В предположении, что поддерживатся присоединенная топология только с одним MPC
                    Bolt_Joint_mesh_elm{i}{j} = Connected_elements;
                    Bolt_Joint_mesh_BS_Elm{i}{j} = Bar_Spider_Elements;
                    Bolt_Joint_mesh_BS_Conn_Nodes{i}{j} = Bar_Spider_Connected_Nodes;
                end
                
            end
            
            if (Fail_flag == 1)
                Chain_screening_marker_table(i) = 1;
                continue;
            end
            
    end

end

fprintf (1, 'OK!\n');

% --- Проверка присоединенной топологии ---

fprintf (1, 'Проверка присоединенной топологии...    ');

for i=1:size(Bolt_Shank_chains,1)
    
    % Если цепочка уже отбракована, то пропускаем ее
    if (Chain_screening_marker_table(i) == 1)
        continue;
    end
    
    % Установка флага
    Fail_flag = 0;
    
    for j=1:size(Bolt_Joint_nodes{i},1)
        
        % Инициализация списка проверок
        Check_RBE2_dep_DOF_123456 = 0;
        Check_RBE2_dep_DOF_123_or_123456 = 0;
        Check_bars_non_empty_pset = 0;
        Check_bars_single_pset = 0;
        Check_bars_zero_offsets = 0;
        Check_bars_no_pinned_DOFs = 0;
        Check_bars_pset_bar_or_beam = 0;
        Check_bars_pset_bar_beam_or_rod = 0;
        
        Check_non_empty_pset = 0; %#ok<NASGU>
        Check_single_pset = 0; %#ok<NASGU>
        Check_plate_codirect_normals = 0;
        Check_plate_codirect_mat_orientations = 0;
        Check_plate_deviation_from_bolt_axis = 0;
        Check_isotropic_material = 0;
        Check_equidistant_hole_nodes = 0;
        Check_same_offset = 0;
    
        % Ветвление по типу присоединенной топологии
        switch Bolt_Joint_topology{i}{j}
            
            case 'plate'
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_plate_codirect_normals = 1;
                Check_plate_codirect_mat_orientations = 1;
                Check_plate_deviation_from_bolt_axis = 1;
                Check_same_offset = 1;
                
            case 'plate_rbe2_sequential'
                
                Check_RBE2_dep_DOF_123456 = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_plate_codirect_normals = 1;
                Check_plate_codirect_mat_orientations = 1;
                Check_plate_deviation_from_bolt_axis = 1;
                Check_same_offset = 1;
                
            case 'plate_rbe2_parallel'
                
                Check_RBE2_dep_DOF_123456 = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_plate_codirect_normals = 1;
                Check_plate_codirect_mat_orientations = 1;
                Check_plate_deviation_from_bolt_axis = 1;
                Check_same_offset = 1;
                
            case 'rbe2_2d_hole'
                
                Check_RBE2_dep_DOF_123456 = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_plate_codirect_normals = 1;
                Check_plate_codirect_mat_orientations = 1;
                Check_plate_deviation_from_bolt_axis = 1;
                Check_equidistant_hole_nodes = 1;
                Check_same_offset = 1;
                
                % Не проверяем, что независимый узел RBE2 лежит на оси симметрии отверстия в пределах заданного допуска
                
                % Не проверяем, что ось симметрии отверстия параллельна оси болта в пределах заданного углового допуска
                % (вместо этого выполняется проверка отклонения нормалей shell элементов от оси болта,
                %  а также проверка равноудаленности узлов отверстия от стержня болта)
                
            case 'rbe2_3d_hole'
                
                Check_RBE2_dep_DOF_123_or_123456 = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_isotropic_material = 1;
                Check_equidistant_hole_nodes = 1;
                
                % Не проверяем, что независимый узел RBE2 лежит на оси симметрии отверстия в пределах заданного допуска
                
                % Не проверяем, что ось симметрии отверстия параллельна оси болта в пределах заданного углового допуска
                % (вместо этого выполняется проверка равноудаленности узлов отверстия от стержня болта)
                
            case 'bar_spider_2d_hole'
                
                Check_bars_non_empty_pset = 1;
                Check_bars_single_pset = 1;
                Check_bars_zero_offsets = 1;
                Check_bars_no_pinned_DOFs = 1;
                Check_bars_pset_bar_or_beam = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_plate_codirect_normals = 1;
                Check_plate_codirect_mat_orientations = 1;
                Check_plate_deviation_from_bolt_axis = 1;
                Check_equidistant_hole_nodes = 1;
                Check_same_offset = 1;
                
                % Не проверяем, что центральный узел паука лежит на оси симметрии отверстия в пределах заданного допуска
                
                % Не проверяем, что ось симметрии отверстия параллельна оси болта в пределах заданного углового допуска
                % (вместо этого выполняется проверка отклонения нормалей shell элементов от оси болта,
                %  а также проверка равноудаленности узлов отверстия от стержня болта)
                
            case 'bar_spider_3d_hole'
                
                Check_bars_non_empty_pset = 1;
                Check_bars_single_pset = 1;
                Check_bars_zero_offsets = 1;
                Check_bars_no_pinned_DOFs = 1;
                Check_bars_pset_bar_beam_or_rod = 1;
                
                Check_non_empty_pset = 1;
                Check_single_pset = 1;
                Check_isotropic_material = 1;
                Check_equidistant_hole_nodes = 1;
                
                % Не проверяем, что центральный узел паука лежит на оси симметрии отверстия в пределах заданного допуска
                
                % Не проверяем, что ось симметрии отверстия параллельна оси болта в пределах заданного углового допуска
                % (вместо этого выполняется проверка равноудаленности узлов отверстия от стержня болта)
                
            otherwise
                
                fprintf (1, '\nОбнаружен неизвестный тип присоединенной топологии [%s] !\n', Bolt_Joint_topology{i}{j});
                ErrCode = -1;
                return;
                
        end
        
        % --- Выполнение проверок MPC и балочных элементов ---
        
        % Проверка степеней свободы зависимых узлов RBE2 (123456)
        if (Check_RBE2_dep_DOF_123456 == 1)
            
            if (~Compare_DOFs(DB_MPC{AT_MPC(Bolt_Joint_mesh_MPC{i}{j})}.dep_DOF, 123456))
                Fail_flag = 1;
                if (strcmp(Bolt_Joint_topology{i}{j}, 'rbe2_2d_hole'))
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка степеней свободы зависимых узлов RBE2).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке из оболочечных элементов с "пауком" из RBE2,\n') ...
                                                       sprintf('степени свободы зависимых узлов которого отличны от 123456.\n') ...
                                                       sprintf('В данном случае степени свободы зависимых узлов RBE2 должны быть 123456.')];
                else
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка степеней свободы зависимых узлов RBE2).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт крепится к сетке из оболочечных элементов, локально усиленных с помощью RBE2,\n') ...
                                                       sprintf('степени свободы зависимых узлов которого отличны от 123456.\n') ...
                                                       sprintf('В данном случае степени свободы зависимых узлов RBE2 должны быть 123456.')];
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка степеней свободы зависимых узлов RBE2 (123 или 123456)
        if (Check_RBE2_dep_DOF_123_or_123456 == 1)
            
            if (~(Compare_DOFs(DB_MPC{AT_MPC(Bolt_Joint_mesh_MPC{i}{j})}.dep_DOF, 123) || Compare_DOFs(DB_MPC{AT_MPC(Bolt_Joint_mesh_MPC{i}{j})}.dep_DOF, 123456)))
                Fail_flag = 1;
                Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка степеней свободы зависимых узлов RBE2).\n', j) ...
                                                   sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке из твердотельных элементов с "пауком" из RBE2,\n') ...
                                                   sprintf('степени свободы зависимых узлов которого отличны от 123 и 123456.\n') ...
                                                   sprintf('В данном случае степени свободы зависимых узлов RBE2 должны быть 123456, либо 123.')];
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка непустого property set балочных элементов
        if (Check_bars_non_empty_pset == 1)
            
            for k=1:size(Bolt_Joint_mesh_BS_Elm{i}{j},1)
                if (isempty(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(k))}.Prop_ID))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка непустого property set балочных элементов).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов, которым не присвоен property set.\n') ...
                                                       sprintf('Все балочные элементы в "пауке" должны иметь непустой property set.')];
                    break;
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка одинакового property set балочных элементов
        if (Check_bars_single_pset == 1)
            
            First_elm_pset = DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(1))}.Prop_ID)}.ID;

            for k=1:size(Bolt_Joint_mesh_BS_Elm{i}{j},1)
                if(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(k))}.Prop_ID)}.ID ~= First_elm_pset)
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка одинакового property set балочных элементов).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов с разными property set.\n') ...
                                                       sprintf('Все балочные элементы в "пауке" должны иметь одинаковый property set, отличный от property set стержня болта.')];
                    break;
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка нулевых офсетов балочных элементов
        if (Check_bars_zero_offsets == 1)
            
            % Проверка только балочных элементов со свойствами bar/beam
            Bar_pset_flag = 0;
            switch DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(1))}.Prop_ID)}.type
                case {'PBMSECT', 'PBRSECT', 'PBAR', 'PBARL', 'PBEAM', 'PBEAML', 'PBCOMP'}
                    Bar_pset_flag = 1;
            end

            if (Bar_pset_flag == 1)

                for k=1:size(Bolt_Joint_mesh_BS_Elm{i}{j},1)

                    % У балочных элементов должны быть нулевые офсеты
                    [Offset_vector_A, Offset_vector_B] = Get_Bar_Offset_Vectors(Bolt_Joint_mesh_BS_Elm{i}{j}(k));

                    if ((~isempty(Offset_vector_A)) && ((Offset_vector_A(1) ~= 0) || (Offset_vector_A(2) ~= 0) || (Offset_vector_A(3) ~= 0)))
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка нулевых офсетов балочных элементов).\n', j) ...
                                                           sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов с ненулевыми офсетами.\n') ...
                                                           sprintf('Все балочные элементы в "пауке" должны иметь нулевые офсеты.')];
                        break;
                    end
                    if ((~isempty(Offset_vector_B)) && ((Offset_vector_B(1) ~= 0) || (Offset_vector_B(2) ~= 0) || (Offset_vector_B(3) ~= 0)))
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка нулевых офсетов балочных элементов).\n', j) ...
                                                           sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов с ненулевыми офсетами.\n') ...
                                                           sprintf('Все балочные элементы в "пауке" должны иметь нулевые офсеты.')];
                        break;
                    end

                end

            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка отсутствия Pinned DOFs у балочных элементов
        if (Check_bars_no_pinned_DOFs == 1)
            
            % Проверка только балочных элементов со свойствами bar/beam
            Bar_pset_flag = 0;
            switch DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(1))}.Prop_ID)}.type
                case {'PBMSECT', 'PBRSECT', 'PBAR', 'PBARL', 'PBEAM', 'PBEAML', 'PBCOMP'}
                    Bar_pset_flag = 1;
            end

            if (Bar_pset_flag == 1)

                for k=1:size(Bolt_Joint_mesh_BS_Elm{i}{j},1)

                    % У балочных элементов не должно быть Pinned DOFs
                    [Pinned_DOFs_A, Pinned_DOFs_B] = Get_Bar_Pinned_DOFs(Bolt_Joint_mesh_BS_Elm{i}{j}(k));

                    if (~isempty(Pinned_DOFs_A))
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка отсутствия Pinned DOFs у балочных элементов).\n', j) ...
                                                           sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов, у которых заданы Pinned DOFs.\n') ...
                                                           sprintf('Все балочные элементы в "пауке" не должны иметь Pinned DOFs.')];
                        break;
                    end
                    if (~isempty(Pinned_DOFs_B))
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка отсутствия Pinned DOFs у балочных элементов).\n', j) ...
                                                           sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке с "пауком" из балочных элементов, у которых заданы Pinned DOFs.\n') ...
                                                           sprintf('Все балочные элементы в "пауке" не должны иметь Pinned DOFs.')];
                        break;
                    end

                end

            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка типа property set балочных элементов (bar, либо beam)
        if (Check_bars_pset_bar_or_beam == 1)
            
            switch DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(1))}.Prop_ID)}.type
                case {'PBMSECT', 'PBRSECT', 'PBAR', 'PBARL', 'PBEAM', 'PBEAML', 'PBCOMP'}
                    % Все хорошо
                otherwise
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка типа property set балочных элементов).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке из оболочечных элементов с "пауком" из балочных элементов,\n') ...
                                                       sprintf('тип property set которых отличен от PBAR, PBARL, PBEAM, PBEAML, PBRSECT, PBMSECT, PBCOMP.\n') ...
                                                       sprintf('Все балочные элементы в "пауке" должны иметь один из вышеперечисленных типов property set.')];
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка типа property set балочных элементов (bar, beam, либо rod)
        if (Check_bars_pset_bar_beam_or_rod == 1)
            
            switch DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_BS_Elm{i}{j}(1))}.Prop_ID)}.type
                case 'PROD'
                    % Все хорошо
                case {'PBMSECT', 'PBRSECT', 'PBAR', 'PBARL', 'PBEAM', 'PBEAML', 'PBCOMP'}
                    % Все хорошо
                otherwise
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка типа property set балочных элементов).\n', j) ...
                                                       sprintf('На одном из соединяемых ярусов болт установлен в явное отверстие в сетке из твердотельных элементов с "пауком" из балочных элементов,\n') ...
                                                       sprintf('тип property set которых отличен от PBAR, PBARL, PBEAM, PBEAML, PBRSECT, PBMSECT, PBCOMP, PROD.\n') ...
                                                       sprintf('Все балочные элементы в "пауке" должны иметь один из вышеперечисленных типов property set.')];
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % --- Выполнение проверок сетки ---
        
        % Проверка непустого property set элементов сетки
        if (Check_non_empty_pset == 1)

            for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                if (isempty(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(k))}.Prop_ID))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка непустого property set).\n', j) ...
                                                       sprintf('Это означает, что на одном из соединяемых ярусов к болту примыкает элемент, которому не присвоен property set.\n') ...
                                                       sprintf('Все конечные элементы, примыкающие к болту на каждом ярусе, должны иметь непустой property set.')];
                    break;
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка одинакового property set элементов сетки
        if (Check_single_pset == 1)

            First_elm_pset = DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(1))}.Prop_ID)}.ID;

            for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                if(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(k))}.Prop_ID)}.ID ~= First_elm_pset)
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка одинакового property set).\n', j) ...
                                                       sprintf('Это означает, что на одном из соединяемых ярусов к болту примыкают элементы с разными property set.\n') ...
                                                       sprintf('Все конечные элементы, примыкающие к болту на отдельном ярусе, должны иметь одинаковый property set.')];
                    break;
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка сонаправленности нормалей shell элементов
        if (Check_plate_codirect_normals == 1)
            
            Ref_normal = Get_Shell_Normal(Bolt_Joint_mesh_elm{i}{j}(1));
                        
            for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                
                Current_normal = Get_Shell_Normal(Bolt_Joint_mesh_elm{i}{j}(k));
                
                Angle = acosd(dot(Ref_normal, Current_normal)); % Нормали имеют единичную длину

                if (Angle > Plate_normal_deg_tolerance)
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка сонаправленности нормалей оболочечных элементов).\n', j) ...
                                                       sprintf('Угол между нормалями некоторых оболочечных элементов, окружающих болт на одном из ярусов, составляет %f%s,\n', Angle, char(176)) ...
                                                       sprintf('в то время как максимальный допускаемый угол составляет %f%s.', Plate_normal_deg_tolerance, char(176))];
                    break;
                end
                
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка сонаправленности векторов ориентации материала у shell элементов
        if (Check_plate_codirect_mat_orientations == 1)
            
            switch DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(1))}.Prop_ID)}.type
                case {'PCOMP', 'PCOMPG'}
                    Check_Flag = 1;
                case 'PSHELL'
                    if (strcmp(DB_MATERIALS{AT_MATERIALS(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(1))}.Prop_ID)}.Mat_ID_membrane)}.type, 'isotropic'))
                        Check_Flag = 0;
                    else
                        Check_Flag = 1;
                    end
                otherwise
                    fprintf (1, '\nОбнаружен неизвестный тип property set [%s] при проверке сонаправленности векторов ориентации материала у shell элементов !\n', DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(1))}.Prop_ID)}.type);
                    ErrCode = -1;
                    return;
            end
            
            if (Check_Flag == 1)
            
                Ref_vector = Get_Shell_Orientation_Vector(Bolt_Joint_mesh_elm{i}{j}(1));

                for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)

                    Current_vector = Get_Shell_Orientation_Vector(Bolt_Joint_mesh_elm{i}{j}(k));

                    Angle = acosd(dot(Ref_vector, Current_vector)); % Вектора имеют единичную длину

                    if (Angle > Plate_mat_ori_deg_tolerance)
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка сонаправленности векторов ориентации материала у оболочечных элементов).\n', j) ...
                                                           sprintf('Угол между векторами ориентации материала у некоторых оболочечных элементов, окружающих болт на одном из ярусов, составляет %f%s,\n', Angle, char(176)) ...
                                                           sprintf('в то время как максимальный допускаемый угол составляет %f%s.', Plate_mat_ori_deg_tolerance, char(176))];
                        break;
                    end

                end
            
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка отклонения нормалей shell элементов от оси болта
        if (Check_plate_deviation_from_bolt_axis == 1)
            
            % Если болт не нулевой длины
            if (~Equiv_Check_Cube(Bolt_Shank_chains{i}.Nodes(1), Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes)))
                
                % Направляющий вектор оси болта
                Bolt_shank_vector = DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(1))}.Location - DB_NODES{AT_NODES(Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes))}.Location;
                Bolt_shank_vector = Bolt_shank_vector / norm(Bolt_shank_vector);
                
                % Проверка нормалей
                for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                    
                    Current_normal = Get_Shell_Normal(Bolt_Joint_mesh_elm{i}{j}(k));
                    
                    Angle = acosd(dot(Bolt_shank_vector, Current_normal)); % Нормали имеют единичную длину
                    
                    if (Angle > 90)
                        Angle = 180 - Angle;
                    end
                    
                    if (Angle > Bolt_plate_deg_tolerance)
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка отклонения нормалей оболочечных элементов от оси болта).\n', j) ...
                                                           sprintf('Угол между нормалью одного из оболочечных элементов, окружающих болт на одном из ярусов, и осью болта составляет %f%s,\n', Angle, char(176)) ...
                                                           sprintf('в то время как максимальный допускаемый угол составляет %f%s.', Bolt_plate_deg_tolerance, char(176))];
                        break;
                    end
                    
                end
                
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка изотропного материала в solid элементах
        if (Check_isotropic_material == 1)
            
            for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                if (~strcmp(DB_MATERIALS{AT_MATERIALS(DB_PSETS{AT_PSETS(DB_ELEMENTS{AT_ELEMENTS(Bolt_Joint_mesh_elm{i}{j}(k))}.Prop_ID)}.Mat_ID)}.type, 'isotropic'))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка изотропного материала в твердотельных элементах).\n', j) ...
                                                       sprintf('Это означает, что на одном из соединяемых ярусов к болту примыкают твердотельные элементы, которым присвоен не изотропный материал.\n') ...
                                                       sprintf('Всем твердотельным элементам, примыкающим к болту, должен быть присвоен изотропный материал.')];
                    break;
                end
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка равноудаленности узлов отверстия от стержня болта
        if (Check_equidistant_hole_nodes == 1)
            
            % Если болт не нулевой длины
            if (~Equiv_Check_Cube(Bolt_Shank_chains{i}.Nodes(1), Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes)))
                
                switch Bolt_Joint_topology{i}{j}
                    
                    case {'rbe2_2d_hole', 'rbe2_3d_hole'}
                        Hole_nodes_array = DB_MPC{AT_MPC(Bolt_Joint_mesh_MPC{i}{j})}.dep_grid;
                        
                    case {'bar_spider_2d_hole', 'bar_spider_3d_hole'}
                        Hole_nodes_array = Bolt_Joint_mesh_BS_Conn_Nodes{i}{j};
                        
                    otherwise
                        fprintf (1, '\nОбнаружен неизвестный тип присоединенной топологии [%s] при попытке выполнения проверки равноудаленности узлов отверстия от стержня болта !\n', Bolt_Joint_topology{i}{j});
                        ErrCode = -1;
                        return;
                        
                end
                                
                % Выделение памяти
                Distances = zeros(size(Hole_nodes_array,1),1);
                
                % Вычисление расстояний от узлов отверстия до стержня болта
                for k=1:size(Hole_nodes_array,1)
                    Distances(k) = Get_Point_Line_Distance(Hole_nodes_array(k), Bolt_Shank_chains{i}.Nodes(1), Bolt_Shank_chains{i}.Nodes(Bolt_Shank_chains{i}.N_nodes));
                end
                
                Average_distance = mean(Distances);
                
                Dist_lower_bound = Average_distance * (1 - Equidistant_hole_tolerance);
                Dist_upper_bound = Average_distance * (1 + Equidistant_hole_tolerance);
                
                for k=1:size(Distances,1)
                    
                    if ((Distances(k) < Dist_lower_bound) || (Distances(k) > Dist_upper_bound))
                        Fail_flag = 1;
                        Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка равноудаленности узлов отверстия от стержня болта).\n', j) ...
                                                           sprintf('Это означает, что на одном из соединяемых ярусов болт установлен в явное отверстие,\n') ...
                                                           sprintf('узлы на кромке/стенке которого не равноудалены от стержня болта в пределах заданного допуска.')];
                        break;
                    end
                    
                end
                
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
        
        % Проверка одинакового офсета
        if (Check_same_offset == 1)
            
            First_elm_offset = Get_Plate_Offset(Bolt_Joint_mesh_elm{i}{j}(1));
            
            for k=1:size(Bolt_Joint_mesh_elm{i}{j},1)
                
                Current_elm_offset = Get_Plate_Offset(Bolt_Joint_mesh_elm{i}{j}(k));
                
                if (abs(Current_elm_offset - First_elm_offset) > Offset_relative_tolerance * abs(First_elm_offset))
                    Fail_flag = 1;
                    Chain_screening_reason_table{i} = [sprintf('Не пройдена проверка окружающей болт сетки на ярусе %i (проверка одинакового офсета оболочечных элементов).\n', j) ...
                                                       sprintf('Это означает, что на одном из соединяемых ярусов к болту примыкают оболочечные элементы с разными офсетами.\n') ...
                                                       sprintf('Все оболочечные элементы, примыкающие к болту на отдельном ярусе, должны иметь одинаковые офсеты.')];
                    break;
                end
                
            end
            
        end
        
        if (Fail_flag == 1)
            break;
        end
    
    end
    
    if (Fail_flag == 1)
        Chain_screening_marker_table(i) = 1;
        continue;
    end
    
end

fprintf (1, 'OK!\n');

% Запись диагностической информации

Diagnostic_Data.Chain_Screening_Marker_Table = Chain_screening_marker_table;
Diagnostic_Data.Chain_Screening_Reason_Data = Chain_screening_reason_table;

% --- Формирование базы данных крепежа ---

fprintf (1, 'Формирование базы данных крепежа...     ');

counter = 0;

for i=1:size(Chain_screening_marker_table,1)
    if (Chain_screening_marker_table(i) == 0)
        counter = counter + 1;
    end
end

if (counter == 0)
    fprintf (1, '\nВ модели не удалось найти крепеж !\n');
    ErrCode = -1;
    return;
end

DB_FASTENERS = cell(counter,1);

counter = 0;
Stat_bar = 0;
Stat_rutman = 0;

for i=1:size(Bolt_Shank_chains,1)

    if (Chain_screening_marker_table(i) == 0)
        
        counter = counter + 1;
        
        DB_FASTENERS{counter}.topology = Bolt_topology{i};
        DB_FASTENERS{counter}.N_layers = [];
        
        switch DB_FASTENERS{counter}.topology
            case 'bar_fastener'
                DB_FASTENERS{counter}.N_layers = Bolt_Shank_chains{i}.N_nodes;
                Stat_bar = Stat_bar + 1;
            case 'rutman_fastener'
                DB_FASTENERS{counter}.N_layers = Bolt_Shank_chains{i}.N_nodes-2;
                Stat_rutman = Stat_rutman + 1;
            otherwise
                fprintf (1, '\nОбнаружен неизвестный тип топологии болта при заполнении полей базы данных [%s] !\n', DB_FASTENERS{counter}.topology);
                ErrCode = -1;
                return;
        end
        
        DB_FASTENERS{counter}.Shank_nodes = Bolt_Shank_chains{i}.Nodes;
        DB_FASTENERS{counter}.Shank_elements = Bolt_Shank_chains{i}.Elements;
        
        DB_FASTENERS{counter}.Additional_nodes = Bolt_Midsurface_nodes{i};
        DB_FASTENERS{counter}.RBAR_MPCs = Bolt_RBAR_MPCs{i};
        DB_FASTENERS{counter}.Head_MPC_Index = Bolt_Head_MPC_Index{i};
        DB_FASTENERS{counter}.Bush_elements = Bolt_Bush_elements{i};
        
        DB_FASTENERS{counter}.Offset_MPCs = Bolt_Offset_MPC{i};
        
        DB_FASTENERS{counter}.Joint_nodes = Bolt_Joint_nodes{i};
        DB_FASTENERS{counter}.Joint_topology = Bolt_Joint_topology{i};
        DB_FASTENERS{counter}.Joint_mesh = cell(DB_FASTENERS{counter}.N_layers,1);
        
        for j=1:DB_FASTENERS{counter}.N_layers
            
            DB_FASTENERS{counter}.Joint_mesh{j}.MPC_ID = Bolt_Joint_mesh_MPC{i}{j};
            DB_FASTENERS{counter}.Joint_mesh{j}.Bar_Spider_Elm_list = Bolt_Joint_mesh_BS_Elm{i}{j};
            DB_FASTENERS{counter}.Joint_mesh{j}.Bar_Spider_Conn_Nodes_list = Bolt_Joint_mesh_BS_Conn_Nodes{i}{j};
            DB_FASTENERS{counter}.Joint_mesh{j}.Element_list = Bolt_Joint_mesh_elm{i}{j};
            
        end
        
    end
    
end

fprintf (1, 'OK!\n');

% --- Вывод статистики ---

fprintf (1, '\nСтатистика крепежа:\n\n');
fprintf (1, '    Найдено единиц крепежа: %i\n', size(DB_FASTENERS,1));
fprintf (1, '            Балочные болты: %i\n', Stat_bar);
fprintf (1, '             Болты Рутмана: %i\n', Stat_rutman);
